package com.zzzzzz.account.service;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import com.zzzzzz.account.dao.UserDao;
import com.zzzzzz.account.model.EmailSend;
import com.zzzzzz.account.model.PortraitForm;
import com.zzzzzz.account.model.Ticket;
import com.zzzzzz.account.model.User;
import com.zzzzzz.common.util.Constants;
import com.zzzzzz.common.util.FileUtils;
import com.zzzzzz.common.util.StringPool;
import com.zzzzzz.common.util.Validator;
import com.zzzzzz.core.data.filestore.FileData;
import com.zzzzzz.core.data.filestore.FileStorage;
import com.zzzzzz.core.exception.SystemException;
import com.zzzzzz.core.exception.UserActiveException;
import com.zzzzzz.core.exception.UserPasswordException;
import com.zzzzzz.core.exception.ZzzzzzException;

@Service("userService")
public class UserServiceImpl implements UserService {
	
	@Resource
	private UserDao userDao;
	@Resource
	private TicketService ticketService;
	@Resource
	private MailService mailService;
	@Resource
	private FileStorage fileStorage;

	public void login(String email, String password, boolean rememberMe) throws ZzzzzzException, SystemException {
		User user = findByEmail(email);
		if(user != null) {
			//is activated
	    	if(user.getStatus() == 1){
				throw new UserActiveException();
	    	}else{
	            Subject currentUser = SecurityUtils.getSubject();
	    		if (!currentUser.isAuthenticated()) {
		            UsernamePasswordToken token = new UsernamePasswordToken(email, password, rememberMe);
		        	currentUser.login(token);
	    		}
	    	}
		}
	}
	
	@Transactional
	public void addAndSendAccountActivationEmail(User user, HttpServletRequest request) {
		add(user);
		sendAccountActivationEmail(user, request);
	}
	
	public void add(User user) {
		user.setStatus(1);
		user.setPassword(new Sha256Hash(user.getPassword()).toHex());
		userDao.add(user);
	}
	
	public void sendAccountActivationEmail(String userId, HttpServletRequest request) throws ZzzzzzException {
		User user = findById(userId);
		sendAccountActivationEmail(user, request);
	}
	
	public void sendAccountActivationEmail(User user, HttpServletRequest request) {
		Date expirationDate = new Date(System.currentTimeMillis() + (7 * 24 * 60 * 60 * 1000)); // one week
		Ticket ticket = ticketService.add(user.getUserId(), Constants.TICKET_TYPE.ACCOUNT_ACTIVATION, expirationDate);
		
		String activationURL = request.getServerName() + StringPool.COLON + request.getServerPort() + request.getContextPath() + "/account/activate/" + ticket.getKey();
		System.out.println("Activation URL: " + activationURL);
		EmailSend emailSend = new EmailSend();
		emailSend.setSendTo(user.getEmail());
		emailSend.setSendFrom(EmailSend.DEFAULT_SENDTO);
		emailSend.setSubject(EmailSend.REGISTER_ACTIVATE_SUBJECT);
		emailSend.setHtmlTemplate(EmailSend.TEMPLATE.CONFIRM_ACCOUNT_TEMPLATE);
		Map<String,Object> htmlData = new HashMap<String,Object>();
		htmlData.put("username", user.getUsername());
		htmlData.put("activation_url", activationURL);
		emailSend.setHtmlData(htmlData);
		mailService.sendMail(emailSend);
	}
	
	public void sendResetPasswordEmail(String email, HttpServletRequest request) throws ZzzzzzException {
		User user = findByEmail(email);
		Date expirationDate = new Date(System.currentTimeMillis() + (24 * 60 * 60 * 1000));
		Ticket ticket = ticketService.add(user.getUserId(), Constants.TICKET_TYPE.PASSWORD_RESET, expirationDate);
		String resetPasswordURL = request.getServerName() + StringPool.COLON + request.getServerPort() + request.getContextPath() + "/account/reset_password/" + ticket.getKey();
		System.out.println("resend password url: " + resetPasswordURL);

		// send reset password email
		EmailSend emailSend = new EmailSend();
		emailSend.setSendTo(user.getEmail());
		emailSend.setSendFrom(EmailSend.DEFAULT_SENDTO);
		emailSend.setSubject(EmailSend.REGISTER_ACTIVATE_SUBJECT);
		emailSend.setHtmlTemplate(EmailSend.TEMPLATE.RESET_PASSWORD_TEMPLATE);
		Map<String,Object> htmlData = new HashMap<String,Object>();
		htmlData.put("reset_password_url", resetPasswordURL);
		emailSend.setHtmlData(htmlData);
		mailService.sendMail(emailSend);
	}
	
	public void resetPassword(String userId, String newPassword, String reNewPassword) throws ZzzzzzException {
		if (Validator.isNull(newPassword) || Validator.isNull(reNewPassword)) {
			throw new UserPasswordException(UserPasswordException.PASSWORD_INVALID);
		}
		if(!newPassword.equals(reNewPassword)) {
        	throw new UserPasswordException(UserPasswordException.PASSWORDS_DO_NOT_MATCH);
		}
		
		User user = findById(userId);
		String curEncPwd = user.getPassword();
		String newEncPwd = new Sha256Hash(newPassword).toHex();
		if(curEncPwd.equals(newEncPwd)) {
			throw new UserPasswordException(UserPasswordException.PASSWORD_SAME_AS_CURRENT);
		}
		userDao.resetPassword(userId, newEncPwd);
	}
	
	public User findById(String id) throws ZzzzzzException {
		return userDao.findById(id);
	}
	
	public Boolean isSignedIn() {
		Subject currentUser = SecurityUtils.getSubject();
		return currentUser.isAuthenticated();
	}
	
	public User findCurrentUser() throws ZzzzzzException {
//		Subject currentUser = SecurityUtils.getSubject();
//		Session session = currentUser.getSession();
//		User user = (User) session.getAttribute("currentUser");
//		if(user == null) {
//			String currentUserId = (String) currentUser.getPrincipal();
//			if( currentUserId != null ) {
//				user = findById(currentUserId);
//	    		session.setAttribute("currentUser", user);
//			}
//		}
//		return user;
		Subject currentUser = SecurityUtils.getSubject();
		User user = null;
		String currentUserId = (String) currentUser.getPrincipal();
		if( currentUserId != null ) {
			user = findById(currentUserId);
		}
		return user;
	}
	
	public String findCurrentUserId() {
		return (String) SecurityUtils.getSubject().getPrincipal();
	}
	
	public User findByEmail(String email) throws ZzzzzzException {
		return userDao.findByEmail(email);
	}

	public User activate(String userId) throws SystemException, ZzzzzzException {
		User user = findById(userId);
		if(user.getStatus() == 1) {
			this.editStatus(userId, 0);
		}
		return user;
	}
	
	public void editStatus(String userId, Integer status) {
		userDao.editStatus(userId, status);
	}
	
	public void editProfileBasic(User user) {
		userDao.editProfileBasic(user);
	}
	
	public void editAccountGeneral(User user, String givenPassword) throws ZzzzzzException {
		if (Validator.isNull(givenPassword)) {
			throw new UserPasswordException(UserPasswordException.PASSWORDS_WRONG);
		}
		String curEncPwd = user.getPassword();
		if(!curEncPwd.equals(new Sha256Hash(givenPassword).toHex())) {
			throw new UserPasswordException(UserPasswordException.PASSWORDS_WRONG);
		}
		
		userDao.editAccountGeneral(user);
	}

	public void editPassword(User user, String givenPassword, String newPassword, String reNewPassword) throws ZzzzzzException {
		if (Validator.isNull(givenPassword)) {
			throw new UserPasswordException(UserPasswordException.PASSWORD_INVALID);
		}
		String curEncPwd = user.getPassword();
		if(!curEncPwd.equals(new Sha256Hash(givenPassword).toHex())) {
			throw new UserPasswordException(UserPasswordException.PASSWORDS_WRONG);
		}
		String newEncPwd = new Sha256Hash(newPassword).toHex();
		
		if (Validator.isNull(newPassword) || Validator.isNull(reNewPassword)) {
			throw new UserPasswordException(UserPasswordException.PASSWORD_INVALID);
		}
		if(!newPassword.equals(reNewPassword)) {
        	throw new UserPasswordException(UserPasswordException.PASSWORDS_DO_NOT_MATCH);
		}
		if(curEncPwd.equals(newEncPwd)) {
			throw new UserPasswordException(UserPasswordException.PASSWORD_SAME_AS_CURRENT);
		}
		userDao.resetPassword(user.getUserId(), newEncPwd);
	}

	public void editPortrait(PortraitForm portraitForm) throws ZzzzzzException, IOException  {
//		FileData fileData = FileUtils.getFileData(portraitForm.getFiles());
		CommonsMultipartFile files = portraitForm.getFiles();
		if(!files.isEmpty()) {
			// remove current portrait and thumbnail
			removePortrait();
			FileData portraitData = FileUtils.getResizedImageData(files, Constants.IMAGE.USER_PORTRAIT_SIDE_LENGTH, Constants.IMAGE.USER_PORTRAIT_SIDE_LENGTH, true);
			String portraitId = fileStorage.storeFile(portraitData);
			String userId = this.findCurrentUserId();
			userDao.editPortrait(userId , portraitId);

			FileData thumbnailData = FileUtils.getResizedImageData(files, Constants.IMAGE.USER_THUMBNAIL_SIDE_LENGTH, Constants.IMAGE.USER_THUMBNAIL_SIDE_LENGTH, true);
			String thumbnailId = fileStorage.storeFile(thumbnailData);
			userDao.editThumbnail(userId, thumbnailId);
		}
	}
	
	public void removePortrait() throws ZzzzzzException {
		User user = this.findCurrentUser();
		fileStorage.deleteFileById(user.getPortraitId());
		fileStorage.deleteFileById(user.getThumbnailId());
		userDao.editPortrait(user.getUserId() , StringPool.BLANK);
		userDao.editThumbnail(user.getUserId() , StringPool.BLANK);
	}
	
	public String findPortraitIdByUserId() throws ZzzzzzException {
		User user = this.findCurrentUser();
		return user.getPortraitId();
	}
	
	public String findThumbnailIdByUserId() throws ZzzzzzException {
		User user = this.findCurrentUser();
		return user.getThumbnailId();
	}
	
	public Boolean isEmailExisting(String email) {
		try {
			this.findByEmail(email);
		} catch (ZzzzzzException e) {
			return false;
		}
		return true;
	}

	public List<User> findAll(int start, int offset, boolean ascending) {
		return userDao.findAll(start, offset, ascending);
	}

}
